/** ****************************************************************************
@defgroup ADC ADC
@ingroup BSP
@brief 串口驱动
@addtogroup ADC
@{
*******************************************************************************/

#include <string.h>
#include "_mcu_adc.h"
#include "share/unios.h"

static uint32_t adc1_dma_buffer_size = 0;
static uint32_t adc3_dma_buffer_size = 0;

DMA_Channel_TypeDef* mcu_get_adc_dma_channel(ADC_TypeDef* adc) {
	/**/ if(adc == ADC1) { return DMA1_Channel1; }
	else if(adc == ADC3) { return DMA2_Channel5; }
	else { return NULL; }
}

static void mcu_set_adc_buffer_size(ADC_TypeDef* adc, uint32_t size) {
	/**/ if(adc == ADC1) { adc1_dma_buffer_size = size; }
	else if(adc == ADC3) { adc3_dma_buffer_size = size; }
}

static uint32_t mcu_get_adc_buffer_size(ADC_TypeDef* adc) {
	/**/ if(adc == ADC1) { return adc1_dma_buffer_size; }
	else if(adc == ADC3) { return adc3_dma_buffer_size; }
	else { return 0; }
}

/**
 * RF ADC 初始化
 * @param adc 使用的 ADC 控制器
 * @param channels ADC 通道号列表(按顺序排列)，长度见 channelCount
 * @param channelCount ADC 通道数
 * @param sampleCount ADC 每通道采样点数量
 * @param samples ADC 采样点数据缓冲区(大小 = channelCount * sampleCount * 2)
 * @param sampleCycles ADC 采样时间(周期数), @see ADC_SampleTime_1Cycles5
 * @param dmaChannel 使用的 DMA 通道
 */
std_err_t mcu_adc_init(ADC_TypeDef* adc, const uint8_t* channels, uint8_t channelCount, uint32_t sampleCount, void* samples, uint8_t sampleCycles, bool dmaMode) {
	// ADC 配置
    ADC_InitTypeDef adccfg;
    adccfg.ADC_Mode               = ADC_Mode_Independent;
    adccfg.ADC_ScanConvMode       = ENABLE;
    adccfg.ADC_ContinuousConvMode = ENABLE;
    adccfg.ADC_ExternalTrigConv   = ADC_ExternalTrigConv_None;
    adccfg.ADC_DataAlign          = ADC_DataAlign_Right;
    adccfg.ADC_NbrOfChannel       = channelCount;
    ADC_Init(adc, &adccfg);

    // ADC 采样通道配置(同时排序)
	for(int i=0; i<channelCount; i++) {
		ADC_RegularChannelConfig(adc, channels[i], i+1, sampleCycles);
	}
	// 打开 ADC
    ADC_Cmd(adc, ENABLE);

	// ADC 校准
	// 1.建议在每次上电后执行一次校准。
	// 2.启动校准前，ADC 必须处于关电状态 (ADON=’0’) 超过至少两个 ADC 时钟周期
    ADC_ResetCalibration(adc);					// 复位校准寄存器
    while(ADC_GetResetCalibrationStatus(adc));	// 等待校准寄存器复位完成
    ADC_StartCalibration(adc);					// ADC校准
    while(ADC_GetCalibrationStatus(adc));		// 等待校准完成

	if(dmaMode) {
		// ADC DMA 配置
		DMA_Channel_TypeDef* dmaChannel = mcu_get_adc_dma_channel(adc);
		DMA_DeInit(dmaChannel);
		DMA_InitTypeDef dmacfg;
		dmacfg.DMA_PeripheralBaseAddr = (uint32_t)&(adc->DR);
		dmacfg.DMA_MemoryBaseAddr     = (uint32_t)samples;					// 采样点数据缓冲区
		dmacfg.DMA_DIR                = DMA_DIR_PeripheralSRC;			// 外设->内存
		dmacfg.DMA_BufferSize         = channelCount * sampleCount;		// 采样点数量
		dmacfg.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;		// 外设地址不递增
		dmacfg.DMA_MemoryInc          = DMA_MemoryInc_Enable;			// 内存地址递增
		dmacfg.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;// 外设数据大小 = 16位 = 2字节
		dmacfg.DMA_MemoryDataSize     = DMA_MemoryDataSize_HalfWord;	// 内存数据大小 = 16位 = 2字节
		dmacfg.DMA_Mode               = DMA_Mode_Circular;				// 循环采样并传输
		dmacfg.DMA_Priority           = DMA_Priority_High;				// 优先级 = 高
		dmacfg.DMA_M2M                = DMA_M2M_Disable;				// 非内存到内存传输
		DMA_Init(dmaChannel, &dmacfg);
		// 保留采样点数量备用
		mcu_set_adc_buffer_size(adc, dmacfg.DMA_BufferSize);			
		// 使能 DMA 通道
		DMA_Cmd(dmaChannel, ENABLE);

		// 打开 DMA 中断(HT - 传输过半; TC - 传输完成; TE - 传输错误)
		DMA_ITConfig(dmaChannel, DMA_IT_HT | DMA_IT_TC | DMA_IT_TE, ENABLE);

		// Enable ADC DMA Request
		ADC_DMACmd(adc, ENABLE);
	}
	
	return STD_ERR_OK;
}

std_err_t mcu_adc_start(ADC_TypeDef* adc) {
	// !!! 注意：启动之前必须先复位当前采样点数，否则会导致数据错位 !!!
	// !!! 注意：ADCx->CNDTR 寄存器只能在通道不工作(DMA_CCRx的EN=0)时写入 !!!
	// 通道开启后该寄存器变为只读，指示剩余的待传输字节数目。寄存器内容在每次DMA传输后递减。
	DMA_Cmd(mcu_get_adc_dma_channel(adc), DISABLE);
	DMA_SetCurrDataCounter(mcu_get_adc_dma_channel(adc), mcu_get_adc_buffer_size(adc));
	DMA_Cmd(mcu_get_adc_dma_channel(adc), ENABLE);
	
	// 使能 ADC
	ADC_Cmd(adc, ENABLE);
	// 软件触发启动 ADC
    ADC_SoftwareStartConvCmd(adc, ENABLE);
	return STD_ERR_OK;
}

std_err_t mcu_adc_stop(ADC_TypeDef* adc) {
	// 关闭 ADC
	ADC_Cmd(adc, DISABLE);
	return STD_ERR_OK;
}


/// @}
